﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace LooseChart
{
    /// <summary>
    /// 二维图形
    /// </summary>
    public abstract class XYSeries : ISeries
    {
        const byte DefaultPointLength = 0x01;
        /// <summary>
        /// 点的宽度
        /// </summary>
        public byte PointLength
        {
            get { return (byte)GetValue(PointLengthProperty); }
            set { SetValue(PointLengthProperty, value); }
        }
        public static readonly DependencyProperty PointLengthProperty = DependencyProperty.Register("PointLength", typeof(byte), typeof(XYSeries), new PropertyMetadata(DefaultPointLength, (d, e) => { if (d is ISeries series) series.Redraw(); }));

        public XYSeries() : base() { }

        #region 数据统计
        /// <summary>
        /// 二维图统计信息及矢量数据
        /// </summary>
        internal ConcurrentBag<PointCount> Statistics { get; set; }
        #endregion

        /// <summary>
        /// 绘制图形显示
        /// </summary>
        /// <param name="chartDataStreams">需要显示的矢量数据流</param>
        internal override void Reshow()
        {
            if (!this.IsLoaded) return;
            ClearTransform();
            using (var dc = DV.RenderOpen())
            {
                if (MainGeometry == null) dc.DrawRectangle(System.Windows.Media.Brushes.Transparent, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
                else DrawDrawingContext(DisplayColor, MainGeometry, dc);
            }
            GC.Collect();
        }

        #region 图刷新
        /// <summary>
        /// 更新数据统计信息
        /// </summary>
        /// <param name="chartDataSource"></param>
        /// <param name="gateArea"></param>
        /// <returns></returns>
        protected override Task UpdateStatisticsData(CancellationToken token)
        {
            if (!IsLoaded || token.IsCancellationRequested || XAxis == null || YAxis == null) return Task.CompletedTask;
            var pointLength = PointLength;
            var maxDegreeOfParallelism = this.MaxDegreeOfParallelism;
            var xParam = XAxis.GetConvertParam();
            Func<double, ValueLocationConvertParam, double> xGetLocation = XAxis.GetValueLocation;
            var yParam = YAxis.GetConvertParam();
            Func<double, ValueLocationConvertParam, double> yGetLocation = YAxis.GetValueLocation;
            var excessDisplayAtEdge = this.ExcessDisplayAtEdge;
            var xsource = XSource;
            var ysource = YSource;
            return Task.Factory.StartNew(() =>
            {
                var temp = PointCountStatistics(token, pointLength, maxDegreeOfParallelism, excessDisplayAtEdge, xsource, XSourceConverter, xParam, xGetLocation, ysource, YSourceConverter, yParam, yGetLocation);
                if (!token.IsCancellationRequested) Statistics = temp;
            }, token);
        }
        #endregion

        /// <summary>
        /// 统计点位数量信息
        /// </summary>
        /// <param name="token"></param>
        /// <param name="pointLength"></param>
        /// <param name="maxDegreeOfParallelism"></param>
        /// <param name="excessDisplayAtEdge"></param>
        /// <param name="xSource"></param>
        /// <param name="xConverter"></param>
        /// <param name="xParam"></param>
        /// <param name="xGetLocation"></param>
        /// <param name="ySource"></param>
        /// <param name="yConverter"></param>
        /// <param name="yParam"></param>
        /// <param name="yGetLocation"></param>
        /// <returns></returns>
        protected virtual ConcurrentBag<PointCount> PointCountStatistics(
            CancellationToken token, byte pointLength, int maxDegreeOfParallelism, bool excessDisplayAtEdge,
            IList xSource, Func<int, double> xConverter, ValueLocationConvertParam xParam, Func<double, ValueLocationConvertParam, double> xGetLocation,
            IList ySource, Func<int, double> yConverter, ValueLocationConvertParam yParam, Func<double, ValueLocationConvertParam, double> yGetLocation)
        {
            if (xSource == null || ySource == null || token.IsCancellationRequested) return null;
            ConcurrentDictionary<uint, int> data = new ConcurrentDictionary<uint, int>();
            int maxwidth = Convert.ToInt32(xParam.Length - pointLength);
            int maxheight = Convert.ToInt32(yParam.Length - pointLength);
            try
            {
                var maxcount = Math.Min(xSource.Count, ySource.Count);
                var result = Parallel.For(0, maxcount, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                {
                    if (loop.IsStopped) return;
                    if (token.IsCancellationRequested) loop.Stop();
                    if (xSource.Count > i && ySource.Count > i)
                    {
                        var x = xGetLocation(xConverter(i), xParam);
                        if (!excessDisplayAtEdge && (double.IsNaN(x) || double.IsInfinity(x) || x < 0 || x > maxwidth)) return;
                        var y = yGetLocation(yConverter(i), yParam);
                        if (!excessDisplayAtEdge && (double.IsNaN(y) || double.IsInfinity(y) || y < 0 || y > maxheight)) return;
                        if (excessDisplayAtEdge)
                        {
                            if (x < 0) x = 0; else if (x > maxwidth) x = maxwidth;
                            if (y < 0) y = 0; else if (y > maxheight) y = maxheight;
                        }
                        var xi = Convert.ToUInt16(Math.Floor(x) / pointLength) * pointLength;
                        var yi = Convert.ToUInt16(Math.Floor(y) / pointLength) * pointLength;
                        uint p = Convert.ToUInt16(xi);
                        p <<= 16;
                        p += Convert.ToUInt16(yi);
                        if (data.ContainsKey(p)) data[p] += 1;
                        else data[p] = 1;
                    }
                });
                if (!result.IsCompleted) return null;
            }
            catch (OperationCanceledException e)
            {
                Debug.WriteLine(string.Concat("Parallel.For", e.Message));
                return null;
            }
            ConcurrentBag<PointCount> pointdatas = new ConcurrentBag<PointCount>();
            foreach (var item in data)
            {
                var p = item.Key;
                pointdatas.Add(new PointCount(new Point(p >> 16, p & 0xffff), item.Value));
            }
            return pointdatas;
        }
    }
    public struct PointCount
    {
        public Point Point { get; set; }
        public int Count { get; set; }
        public PointCount(Point point, int count)
        {
            this.Point = point;
            this.Count = count;
        }
    }
}
